using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using Microsoft.Practices.RepositoryFactory.CodeGeneration.ObjectModel.Mappings.Helper;
using Microsoft.Practices.RepositoryFactory.SchemaDiscovery.ObjectModel.Base;
using Microsoft.Practices.RepositoryFactory.CodeGeneration.ObjectModel.Mappings.Enums;
using Microsoft.Practices.RepositoryFactory.CodeGeneration.ObjectModel;
using Microsoft.Practices.RepositoryFactory.CodeGeneration.ObjectModel.Mappings;
using System.Windows.Forms.Design;
using System.Configuration;
using Microsoft.Practices.RecipeFramework.Extensions.CommonHelpers;
using Microsoft.Practices.RepositoryFactory.CustomPages.Helpers;
using Microsoft.Practices.RepositoryFactory.CodeGeneration.ObjectModel.NamingConventions;
using System.Collections;
using Resources = Microsoft.Practices.RepositoryFactory.Properties.Resources;
using Microsoft.Practices.RepositoryFactory.CustomPages.UI;

namespace Microsoft.Practices.RepositoryFactory.CustomPages
{
    public partial class OutputMappingFormCP : Form
    {
        private OperationType opType = OperationType.InsertOne;
        private Operation operation = Operation.Add;
        private Entity entity;
        private Identity identity; //not initialized on purpose.  Initialized every time LoadMapping() runs.
        private string operationName;
        private ConnectionStringSettings connectionSettings;

        public Entity Entity
        {
            get { return entity; }
            set { entity = value; }
        }

        public string OperationName
        {
            get { return operationName; }
            set { operationName = value; }
        }

        public ConnectionStringSettings ConnectionSettings
        {
            get { return connectionSettings; }
            set { connectionSettings = value; }
        }

        public OutputMappingFormCP(ConnectionStringSettings connectionSettings, Entity entity, OperationType opType)
            :
            this(connectionSettings, entity, opType, Operation.Add, "")
        { }

        public OutputMappingFormCP(ConnectionStringSettings connectionSettings, Entity entity, OperationType opType, Operation operation, string operationName)
        {
            InitializeComponent();

            switch (opType)
            {
                case OperationType.GetAll:
                    txtOperationType.Text = "Get All";
                    break;
                case OperationType.GetMany:
                    txtOperationType.Text = "Get Many";
                    break;
                case OperationType.GetOne:
                    txtOperationType.Text = "Get One";
                    break;
                case OperationType.GetOneComplex:
                    txtOperationType.Text = "Get One Complex";
                    break;
                case OperationType.DeleteOne:
                    txtOperationType.Text = "Delete";
                    getControlsPanel.Visible = false;
                    break;
                default:
                    break;
            }

            lstInputParameters.Enabled = false;
            lstInputParameters.BackColor = SystemColors.Control;
            this.entity = entity;
            this.opType = opType;
            this.operation = operation;
            this.operationName = operationName;
            this.connectionSettings = connectionSettings;

            SetControls();
            cmbStoredProcedures.Items.Clear();

            Cursor.Current = Cursors.WaitCursor;

            try
            {
                List<StoredProcedure> storedProcedures =
                    UIHelper.GetStoredProcedures(connectionSettings);

                UIHelper.FillStoredProceduresCombo(cmbStoredProcedures, storedProcedures);
            }
            catch
            {
                //We cannot use IUIService on an external winform page
                MessageBox.Show(
                    Microsoft.Practices.RepositoryFactory.Properties.Resources.ErrorObtainingSPList,
                    Microsoft.Practices.RepositoryFactory.Properties.Resources.Error);
            }

            Cursor.Current = Cursors.Default;

            if (this.operation == Operation.Edit)
            {
                ProvideValues();
            }
        }

        private void cmbStoredProcedures_SelectedIndexChanged(object sender, EventArgs e)
        {
            lstInputParameters.Items.Clear();
            StoredProcedure sp = (StoredProcedure)cmbStoredProcedures.SelectedItem;

            List<Parameter> parameters =
                UIHelper.GetParameters(
                    connectionSettings,
                    (StoredProcedure)cmbStoredProcedures.SelectedItem,
                    ParameterDirection.Input);

            /////////////////////////////////////////////////////////////////////////////////////////
            // TODO: Added to retrieve IN/OUT params and display them in the INPUT parameters list.
            int index = parameters.Count;
            List<Parameter> parametersInOut =
                UIHelper.GetParameters(
                    connectionSettings,
                    (StoredProcedure)cmbStoredProcedures.SelectedItem,
                    ParameterDirection.InputOutput);
            int count = parametersInOut.Count;
            parameters.AddRange(parametersInOut);
            /////////////////////////////////////////////////////////////////////////////////////////

            UIHelper.FillParametersListBox(lstInputParameters, parameters);

            /////////////////////////////////////////////////////////////////////////////////////////
            // TODO: IN/OUT parameters should be displayed in both the INPUT parameters list
            // and the OUTPUT paramaters list, but they should not be added to the collection
            // of parameters twice (i.e. the list of parameters should be unique).
            // For this reason, once the parameters are added to the listbox for display purposes, 
            // they are removed so as not to be added to the stored procedure mappings twice (keeping 
            // in mind that IN/OUT parameters are added from the OUTPUT paramters listbox).
            parameters.RemoveRange(index, count);
            /////////////////////////////////////////////////////////////////////////////////////////

            if (this.operation == Operation.Edit)
            {
                foreach (EntityStoredProcedureMapping mapping in this.entity.StoredProcedureMappings)
                {
                    if (mapping.Procedure == sp.Name && mapping.OperationName == this.operationName)
                    {
                        RefreshMappings(mapping);
                        break;
                    }
                }
            }
            else
            {
                txtOperationName.Text = sp.Name;
                CleanCombos();
                AutoMapProperties(sp);
                FillCombos();
            }
        }

        private void FillCombos()
        {
            FillProperties(this.entity.Properties);

            List<Parameter> parameters = UIHelper.GetParameters(connectionSettings, (StoredProcedure)cmbStoredProcedures.SelectedItem, ParameterDirection.ReturnValue);
            FillParameters(lstOutputColumns, parameters);
        }

        private void FillParameters(ListBox listBox, List<Parameter> parameters)
        {
            List<Parameter> parametersToAdd = new List<Parameter>();
            foreach (Parameter parameter in parameters)
            {
                if (UIHelper.FindParameter(lstMappings, parameter.Name) == null)
                {
                    parametersToAdd.Add(parameter);
                }
            }
            UIHelper.FillParametersListBox(listBox, parametersToAdd);
        }

        private void FillProperties(List<Property> properties)
        {
            foreach (Property prop in properties)
            {
                if (UIHelper.FindProperty(lstMappings, prop.Name) == null)
                {
                    lstEntityProperties.Items.Add(prop.Name);
                }
            }
        }

        private void AutoMapProperties(StoredProcedure sp)
        {
            foreach (Property property in this.entity.Properties)
            {
                List<Parameter> parameters = UIHelper.GetParameters(connectionSettings, (StoredProcedure)cmbStoredProcedures.SelectedItem, ParameterDirection.ReturnValue);
                foreach (Parameter parameter in parameters)
                {
                    string paramName = ParseParamName(parameter.Name);
                    if (string.Compare(paramName, ParseParamName(property.Name), true) == 0)
                    {
                        PropertyParameterMapping mapping = new PropertyParameterMapping(property.Name,
                            ClonerHelper.CloneParameter(parameter));

                        lstMappings.Items.Add(mapping);
                    }
                }
            }
        }

        private string ParseParamName(string originalParamName)
        {
            return originalParamName.Replace("_", string.Empty);
        }
        private void btnOk_Click(object sender, EventArgs e)
        {
            if (IsInputValid())
            {
                SaveMapping();
                this.DialogResult = DialogResult.OK;
            }
        }

        private void btnAddMapping_Click(object sender, EventArgs e)
        {
            if (!UIGuard.IsListBoxSelected(lstEntityProperties) || !UIGuard.IsListBoxSelected(lstOutputColumns))
            {
                return;
            }

            PropertyParameterMapping mapping =
            new PropertyParameterMapping(
                lstEntityProperties.SelectedItem.ToString(),
                ClonerHelper.CloneParameter((Parameter)lstOutputColumns.SelectedItem));

            lstEntityProperties.Items.Remove(lstEntityProperties.SelectedItem);
            lstOutputColumns.Items.Remove(lstOutputColumns.SelectedItem);
            lstMappings.Items.Add(mapping);

            lstMappings.SelectedIndex = 0;
        }

        private void btnDeleteMapping_Click(object sender, EventArgs e)
        {
            if (!UIGuard.IsListBoxChecked(lstMappings))
            {
                return;
            }

            UIHelper.RemoveMappingItems(lstEntityProperties, lstOutputColumns, lstMappings);
        }

        private void txtOperationName_Leave(object sender, EventArgs e)
        {
            ValidateOperationName();
        }

        private void SetControls()
        {
            cmbStoredProcedures.DisplayMember = Resources.StoredProceduresDisplayMember;
            lstInputParameters.DisplayMember = Resources.ParameterNameDisplayMember;
            lstOutputColumns.DisplayMember = Resources.ParameterNameDisplayMember;
            txtEntity.Text = this.entity.Name;
            txtOperationName.Text = string.Empty;
            txtOperationName.Enabled = (this.operation == Operation.Add);
            cmbStoredProcedures.Enabled = (this.operation == Operation.Add);
            txtOperationName.Focus();
        }

        private void ProvideValues()
        {
            UIHelper.Finder = NamingHelper.GetOperationName(this.operationName);
            EntityStoredProcedureMapping mapping = this.entity.StoredProcedureMappings.Find(UIHelper.FindByOperationName);

            if (mapping != null)
            {
                txtOperationName.Text = mapping.OperationName;
                cmbStoredProcedures.SelectedItem = UIHelper.FindStoredProcedure(cmbStoredProcedures, mapping.Procedure);
            }
        }

        private void SaveMapping()
        {
            const string IDENTITY = "Identity";
            StoredProcedure sp = (StoredProcedure)cmbStoredProcedures.SelectedItem;
            UIHelper.Finder = NamingHelper.GetOperationName(txtOperationName.Text);
            EntityStoredProcedureMapping spMapping = this.entity.StoredProcedureMappings.Find(UIHelper.FindByOperationName);

            // Check for both IN and IN/OUT params.  If either is true, then 
            // the stored proc has input params.
            bool hasInOrInOutParams =
                (UIHelper.GetParameters(this.connectionSettings, sp, ParameterDirection.Input).Count > 0) ||
                (UIHelper.GetParameters(this.connectionSettings, sp, ParameterDirection.InputOutput).Count > 0);
            if (spMapping == null)
            {
                spMapping =
                    new EntityStoredProcedureMapping(
                    sp.Name,
                    hasInOrInOutParams,
                    NamingHelper.GetOperationName(txtOperationName.Text),
                    this.opType);
            }
            else
            {
                this.entity.StoredProcedureMappings.Remove(spMapping);
                spMapping.Procedure = sp.Name;
                spMapping.ProcedureHasInputParameters = hasInOrInOutParams;
                spMapping.OperationName = NamingHelper.GetOperationName(txtOperationName.Text);
                spMapping.ParameterMappings.Clear();
                spMapping.Identity = null;
                spMapping.OperationType = this.opType;
            }

            foreach (object item in lstMappings.Items)
            {
                PropertyParameterMapping mapping = (PropertyParameterMapping)item;

                spMapping.ParameterMappings.Add(mapping);
            }

            identity = new Identity();
            identity.Name = NamingHelper.GetOperationName(txtOperationName.Text) + IDENTITY;

            foreach (object item in lstInputParameters.Items)
            {
                Parameter parameter = (Parameter)item;
                ///////////////////////////////////////////////////////////////////////
                // TODO: Added if condition to filter out IN/OUT parameters.  We don't
                // want to add those to the parameter mappings a second time.
                ///////////////////////////////////////////////////////////////////////
                if (parameter.Direction == ParameterDirection.Input)
                {
                    PropertyParameterMapping mapping =
                        new PropertyParameterMapping(parameter.Name,
                        ClonerHelper.CloneParameter(parameter));

                    identity.ParameterMappings.Add(mapping);
                }
            }

            spMapping.Identity = identity;

            this.entity.StoredProcedureMappings.Add(spMapping);
        }

        private void RefreshMappings(EntityStoredProcedureMapping mapping)
        {
            CleanCombos();

            foreach (PropertyParameterMapping parameterMapping in mapping.ParameterMappings)
            {
                lstMappings.Items.Add(parameterMapping);
            }

            foreach (Property prop in this.entity.Properties)
            {
                if (UIHelper.FindProperty(lstMappings, prop.Name) == null)
                {
                    lstEntityProperties.Items.Add(prop.Name);
                }
            }

            Cursor.Current = Cursors.WaitCursor;

            try
            {
                // parameters can hold OUT parameters, IN/OUT parameters, and a RETURN value
                List<Parameter> parameters =
                    UIHelper.GetParameters(
                        connectionSettings,
                        (StoredProcedure)cmbStoredProcedures.SelectedItem,
                        ParameterDirection.ReturnValue);

                parameters.AddRange(UIHelper.GetParameters(connectionSettings,
                    (StoredProcedure)cmbStoredProcedures.SelectedItem,
                    ParameterDirection.Output));

                parameters.AddRange(UIHelper.GetParameters(connectionSettings,
                    (StoredProcedure)cmbStoredProcedures.SelectedItem,
                    ParameterDirection.InputOutput));



                UIHelper.FillParametersListBox(lstOutputColumns, lstMappings, parameters);
            }
            catch
            {
                //We cannot use IUIService on an external winform page
                MessageBox.Show(
                    Microsoft.Practices.RepositoryFactory.Properties.Resources.ErrorObtainingSPResultSet,
                    Microsoft.Practices.RepositoryFactory.Properties.Resources.Error);
            }

            Cursor.Current = Cursors.Default;
        }

        private void LoadMappings()
        {
            CleanCombos();

            foreach (Property prop in (this.entity.Properties))
            {
                lstEntityProperties.Items.Add(prop.Name);
            }

            Cursor.Current = Cursors.WaitCursor;

            try
            {
                // parameters can hold OUT parameters, IN/OUT parameters, and a RETURN value
                List<Parameter> parameters =
                    UIHelper.GetParameters(
                        connectionSettings,
                        (StoredProcedure)cmbStoredProcedures.SelectedItem,
                        ParameterDirection.ReturnValue);

                parameters.AddRange(UIHelper.GetParameters(connectionSettings,
                    (StoredProcedure)cmbStoredProcedures.SelectedItem,
                    ParameterDirection.Output));

                parameters.AddRange(UIHelper.GetParameters(connectionSettings,
                    (StoredProcedure)cmbStoredProcedures.SelectedItem,
                    ParameterDirection.InputOutput));

                UIHelper.FillParametersListBox(lstOutputColumns, parameters);

                ////////////////////////////////////////////////////////////////////////////////////////
                // With Oracle, all parameters defined by the stored procedure specification 
                // must be provided in the DbCommand object - whether or not they are used.
                // (Passing in null values is accomplished with DbNull.Value).
                // Execution of a DbCommand with an incomplete parameter set results in
                // an Oracle exception.  This explains why all parameters are added to
                // identity.ParameterMappings, regardless of whether they are selected for a mapping
                // to the business entity object.
                // This code was added to LoadMappings() because it needs to be done before 
                // users select parameters for mapping to the business entity.
                identity = new Identity();
                foreach (object item in lstOutputColumns.Items)
                {
                    Parameter parameter = (Parameter)item;
                    PropertyParameterMapping mapping =
                        new PropertyParameterMapping(parameter.Name,
                        ClonerHelper.CloneParameter(parameter));
                    identity.ParameterMappings.Add(mapping);
                }
                /////////////////////////////////////////////////////////////////////////////////////////
            }
            catch
            {
                //We cannot use IUIService on an external winform page
                MessageBox.Show(
                    Microsoft.Practices.RepositoryFactory.Properties.Resources.ErrorObtainingSPResultSet,
                    Microsoft.Practices.RepositoryFactory.Properties.Resources.Error);
            }

            Cursor.Current = Cursors.Default;
        }

        private void CleanCombos()
        {
            lstEntityProperties.Items.Clear();
            lstOutputColumns.Items.Clear();
            lstMappings.Items.Clear();
        }

        private bool ValidateOperationName()
        {
            frmErrorProvider.SetError(txtOperationName, null);
            try
            {
                txtOperationName.Text = CodeDomHelper.ConvertToValidIdentifier(txtOperationName.Text);
                return true;
            }
            catch (Exception ex)
            {
                frmErrorProvider.SetError(txtOperationName, ex.Message);
                return false;
            }
        }

        private bool IsInputValid()
        {
            frmErrorProvider.SetError(txtOperationName, "");
            frmErrorProvider.SetError(cmbStoredProcedures, "");
            frmErrorProvider.SetError(lstMappings, "");

            if (this.operation == Operation.Add)
            {
                if (string.IsNullOrEmpty(NamingHelper.GetOperationName(txtOperationName.Text)))
                {
                    frmErrorProvider.SetError(txtOperationName, Resources.ProvideOpName);
                    return false;
                }
                else if (!ValidateOperationName())
                {
                    return false;
                }

                UIHelper.Finder = NamingHelper.GetOperationName(txtOperationName.Text);
                EntityStoredProcedureMapping mapping = this.entity.StoredProcedureMappings.Find(UIHelper.FindByOperationName);

                if (mapping != null)
                {
                    frmErrorProvider.SetError(txtOperationName, Resources.MappingWithSameOperationName);
                    return false;
                }
                else
                {
                    UIHelper.Finder = ((StoredProcedure)cmbStoredProcedures.SelectedItem).Name;
                    mapping = this.entity.StoredProcedureMappings.Find(UIHelper.FindByStoredProcedureName);

                    if (mapping != null)
                    {
                        frmErrorProvider.SetError(cmbStoredProcedures, Resources.MappingWithSameSPName);
                        return false;
                    }
                }
            }

            if (cmbStoredProcedures.SelectedItem == null)
            {
                frmErrorProvider.SetError(cmbStoredProcedures, Resources.ProvideSP);
                return false;
            }

            if (this.opType != OperationType.DeleteOne)
            {
                if (lstMappings.Items.Count == 0)
                {
                    frmErrorProvider.SetError(lstMappings, Resources.ProvideMapping);
                    return false;
                }
            }

            return true;
        }

        private void btnCancel_Click(object sender, EventArgs e)
        {
            this.DialogResult = DialogResult.Cancel;
            this.Close();
        }
    }
}
